using GameKit.Core;
using OneGame.Gameplay.Input;
using UnityEngine;
using UnityEngine.InputSystem;

namespace OneGame.Gameplay.Player
{
    [RequireComponent(typeof(CharacterController))]
    public class ThirdPersonController : MonoBehaviour
    {
        [SerializeField]
        private float moveSpeed = 6f;

        [SerializeField]
        private float rotationSmoothTime = .12f;

        [Tooltip("Acceleration and deceleration")]
        public float speedChangeRate = 10.0f;

        [SerializeField]
        public Transform cameraFollowTarget;

        [SerializeField]
        public float groundedOffsetY = -0.27f;

        [SerializeField]
        private float groundedRadius = 0.4f;

        [SerializeField]
        private LayerMask groundLayerMask;

        [SerializeField]
        private float gravity = -15f;

        [SerializeField]
        private float jumpHeight = 1.2f;

        private Animator _animator;
        private Vector2 _inputMove;
        private Vector2 _inputLook;
        private bool _inputJump;
        private CharacterController _characterController;
        private static readonly int SpeedHash = Animator.StringToHash("Speed");

        /// <summary>
        /// 移动动画速度系数控制
        /// </summary>
        private static readonly int MotionSpeedHash = Animator.StringToHash("MotionSpeed");

        private static readonly int GroundedHash = Animator.StringToHash("Grounded");
        private static readonly int JumpHash = Animator.StringToHash("Jump");
        private static readonly int FallHash = Animator.StringToHash("Fall");

        private float _rotationVelocity;
        private float _verticalVelocity;
        private Camera _mainCamera;
        private float _speed;
        private float _animationBlend;
        private float _targetRotation;
        private float _cameraYaw;
        private float _cameraPitch;
        private bool _isGrounded;

        /// <summary>
        /// 最大速度，避免一直加速
        /// </summary>
        private const float TerminalVelocity = 53.0f;

        private void Awake()
        {
            _mainCamera = GameObject.FindWithTag("MainCamera").GetComponent<Camera>();
        }

        private void Start()
        {
            _cameraYaw = cameraFollowTarget.transform.rotation.eulerAngles.y;

            _animator = GetComponent<Animator>();
            _characterController = GetComponent<CharacterController>();
            CombatInput.Instance.PlayerActions.Move.performed += OnMove;
            CombatInput.Instance.PlayerActions.Move.canceled += OnMove;
            CombatInput.Instance.PlayerActions.Look.performed += OnLook;
            CombatInput.Instance.PlayerActions.Look.canceled += OnLook;
            CombatInput.Instance.PlayerActions.Jump.performed += OnJump;
            CombatInput.Instance.PlayerActions.Jump.canceled += OnJump;
        }

        private void OnEnable()
        {
            CombatInput.Instance.PlayerInput.Enable();
        }

        private void OnDisable()
        {
            CombatInput.Instance.PlayerInput.Disable();
        }

        public void Update()
        {
            JumpAndGravity();
            GroundedCheck();
            Move();
        }

        private void JumpAndGravity()
        {
            if (_isGrounded)
            {
                _animator.SetBool(JumpHash, false);
                _animator.SetBool(FallHash, false);
                if (_verticalVelocity < 0.0f)
                {
                    _verticalVelocity = -2f;
                }

                if (_inputJump) // 往上跳
                {
                    // v^2 = u^2 + 2as
                    _verticalVelocity = Mathf.Sqrt(-2 * gravity * jumpHeight); // 和重力方向相反
                    _animator.SetBool(JumpHash, true);
                }
            }
            else
            {
                _inputJump = false; // 跳的过程中不能再跳
            }

            // 往下坠落
            if (_verticalVelocity < TerminalVelocity)
            {
                _verticalVelocity += gravity * Time.deltaTime;
            }
        }

        private void GroundedCheck()
        {
            var spherePosition = new Vector3(transform.position.x, transform.position.y - groundedOffsetY, transform.position.z);
            _isGrounded = Physics.CheckSphere(spherePosition, groundedRadius, groundLayerMask, QueryTriggerInteraction.Ignore);
            _animator.SetBool(GroundedHash, _isGrounded);
        }

        private void Move()
        {
            // Move
            var targetSpeed = moveSpeed;
            var moveEnable = _inputMove != Vector2.zero;
            if (!moveEnable)
            {
                targetSpeed = 0;
            }

            // Smoothly change the speed
            var currentHorizontalSpeed = new Vector3(_characterController.velocity.x, 0, _characterController.velocity.z).magnitude;
            const float inputMagnitude = 1f; // FIXME 先固定等于1，否则会出现待机状态动画静止的情况
            if (currentHorizontalSpeed < targetSpeed - 0.1f ||
                currentHorizontalSpeed > targetSpeed + 0.1f)
            {
                _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
                    Time.deltaTime * speedChangeRate);
                // Round speed to 3 decimal places
                _speed = Mathf.Round(_speed * 1000f) / 1000f;
            }
            else
            {
                _speed = targetSpeed;
            }

            // 动作表现的速度和实际的曲线速度分开处理
            _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * speedChangeRate);
            if (_animationBlend < 0.01f) _animationBlend = 0f;

            if (moveEnable)
            {
                // Smoothly rotate the player to the direction it is moving
                var inputDirection = new Vector3(_inputMove.x, 0, _inputMove.y).normalized;
                // 这里反切函数的第一个参数y为什么用x？因为角度0度是和Y轴正方向重合的，所以这里的x和z是反过来的
                var inputAngle = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg;
                // 沿着摄像机的角度叠加输入的方向
                _targetRotation = _mainCamera.transform.eulerAngles.y + inputAngle;
                var angleY = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
                    rotationSmoothTime);
                transform.rotation = Quaternion.Euler(0, angleY, 0);
            }

            // Move the player
            var targetDirection = Quaternion.Euler(0, _targetRotation, 0) * Vector3.forward;
            _characterController.Move(targetDirection.normalized * (_speed * Time.deltaTime) + new Vector3(0, _verticalVelocity, 0) * Time.deltaTime);

            _animator.SetFloat(SpeedHash, _animationBlend);
            _animator.SetFloat(MotionSpeedHash, inputMagnitude);
        }

        private void LateUpdate()
        {
            if (_inputLook.sqrMagnitude > 0.01f)
            {
                float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;
                _cameraYaw += _inputLook.x * deltaTimeMultiplier;
                _cameraPitch += _inputLook.y * deltaTimeMultiplier;
            }

            _cameraYaw = FixAngle(_cameraYaw);
            _cameraPitch = ClampAngle(_cameraPitch, -30f, 70f);
            cameraFollowTarget.rotation = Quaternion.Euler(_cameraPitch, _cameraYaw, 0);
        }

        private bool IsCurrentDeviceMouse => true; // TODO

        private void OnMove(InputAction.CallbackContext context)
        {
            _inputMove = context.ReadValue<Vector2>();
        }

        private void OnLook(InputAction.CallbackContext context)
        {
            _inputLook = context.ReadValue<Vector2>();
        }

        private void OnJump(InputAction.CallbackContext context)
        {
            var jumpValue = context.ReadValue<float>();
            _inputJump = jumpValue != 0;
            Log.Info($"input jump value: {_inputJump}");
        }

        private static float FixAngle(float angle)
        {
            if (angle < -360f) angle += 360f;
            if (angle > 360f) angle -= 360f;
            return angle;
        }

        private static float ClampAngle(float angle, float min, float max)
        {
            angle = FixAngle(angle);
            return Mathf.Clamp(angle, min, max);
        }

        public void FootL()
        {
            // TODO 播放脚步声音
            Log.Info("FootL");
        }

        public void FootR()
        {
            // TODO 播放脚步声音
            Log.Info("FootL");
        }

        public void Land()
        {
            Log.Info("Land");
        }

        private void OnDrawGizmosSelected()
        {
            var green = new Color(0, 1, 0, 0.4f);
            var red = new Color(1, 0, 0, 0.4f);
            Gizmos.color = _isGrounded ? green : red;
            Gizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - groundedOffsetY, transform.position.z), groundedRadius);
        }
    }
}